﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Transactions;
using System.Configuration;
using System.Data.Linq;

namespace JS.Tools
{
	/// <summary>
	/// Wraps Linq data context and sqlconnection in a TransactionScope
	/// Borrowed from private projects, gifted to GPL Molenet tools, (c) 2008 J Sparrow
	/// </summary>
	public class TransactionMan : IDisposable
	{
		public SqlConnection Connection;
		private TransactionScope Scope;
		private List<DataContext> contexts;
		private int timeoutsec;
		private IsolationLevel isolation;

		public TransactionMan(string connectionkey, bool openconnection, IsolationLevel level, int secstimeout)
		{
			this.timeoutsec = secstimeout;
			this.isolation = level;
			SetScope(level, secstimeout);

			ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings[connectionkey];
			if (settings == null) throw new Exception("Cannot find key: " + connectionkey);
			this.Connection = new SqlConnection(settings.ConnectionString);

			if (openconnection)
				this.Connection.Open();
		}

		public TransactionMan(string connectionkey)
			: this(connectionkey, true, IsolationLevel.ReadCommitted, 60)
		{
		}

		public TransactionMan(string connectionkey, IsolationLevel level) 
			: this(connectionkey, true, level, 60)
		{
		}

		private void SetScope(IsolationLevel level, int secstimeout)
		{
			TransactionOptions opt = new TransactionOptions { IsolationLevel = level, Timeout = new TimeSpan(secstimeout * 10000000L) };
			this.Scope = new TransactionScope(TransactionScopeOption.Required, opt);
		}

		/// <summary>
		/// Ensure connection is open, open if not
		/// </summary>
		/// <returns>Original state of connection</returns>
		public bool EnsureConnectionIsOpen()
		{
			bool open = (this.Connection.State != System.Data.ConnectionState.Closed);

			if (!open)
				this.Connection.Open();
			return open;
		}

		/// <summary>
		/// Build a new sqlcommand, using correct connection and transaction
		/// </summary>
		/// <param name="cmd">SQL text</param>
		public SqlCommand BuildCommand(string cmd)
		{
			return new SqlCommand(cmd, this.Connection);
		}

		/// <summary>
		/// Adds a context, to be disposed of when TransactionMan ends
		/// </summary>
		public DataContext AddContext(DataContext con)
		{
			if (this.contexts == null)
				this.contexts = new List<DataContext>(4);
			this.contexts.Add(con);

			return con;
		}

		/// <summary>
		/// Create a datacontext and adds it to context collection for disposal. Uses reflection
		/// </summary>
		public T BuildContext<T>() where T : DataContext
		{
			System.Reflection.ConstructorInfo info = typeof(T).GetConstructor(new Type[] { typeof(System.Data.IDbConnection) });

			T con = (T)info.Invoke(new object[] { this.Connection });
			AddContext(con);
			return con;
		}

		/// <summary>
		/// Marks the transactionscope as good for commit
		/// </summary>
		public void Complete()
		{
			this.Scope.Complete();
		}

		/// <summary>
		/// End this scope with a commit, and start another one
		/// </summary>
		public void CommitAndBegin()
		{
			this.Scope.Complete();
			this.Scope.Dispose();
			SetScope(this.isolation, this.timeoutsec);
		}

		/// <summary>
		/// End this scope with rollback (unless Complete has been called), and start another one
		/// </summary>
		public void DisposeAndBegin()
		{
			this.Scope.Dispose();
			SetScope(this.isolation, this.timeoutsec);
		}

		#region IDisposable Members

		public void Dispose()
		{
			try
			{
				if (this.contexts != null)
					foreach (DataContext c in this.contexts)
						c.Dispose();
				this.contexts = null;
			}
			catch { }
			try
			{
				if (this.Connection != null && this.Connection.State != System.Data.ConnectionState.Closed)
					this.Connection.Close();
				this.Connection = null;
			}
			catch { }

			if (this.Scope != null)
			{
				this.Scope.Dispose();
				this.Scope = null;
			}
		}

		#endregion
	}
}
